/**
 * Copyright (c) 2008 - TheNittyGritty - Thomas Brekelmans
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package nl.thenittygritty.amc 
{
	import flash.display.Loader;
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.net.URLRequest;
	import flash.system.ApplicationDomain;
	import flash.system.LoaderContext;	
	
	/**
	 * ModuleLoader.
	 */
	final public class ModuleLoader extends EventDispatcher
	{
		static private var instance:ModuleLoader;
		
		private var loader:Loader;
		
		private var operations:Array;
		
		/**
		 * Returns the active instance of this class, if no instance is created, 
		 * it is created here before it is returned.
		 */
		static public function getInstance():ModuleLoader
		{
			if (ModuleLoader.instance == null)
			{
				ModuleLoader.instance = new ModuleLoader(
												  	   new SingletonEnforcer());
			}
			return ModuleLoader.instance;
		}
		
		/**
		 * ModuleLoader constructor, uses the SingletonEnforcer to prevent 
		 * instantiation of this class from outside this file.
		 */
		public function ModuleLoader(singletonEnforcer:SingletonEnforcer)
		{
			super(this);
			
			initLoader();
		}
		
		/**
		 * Creates the loader instance and defines all the necessary listeners 
		 * for it and it's load
		 */
		private function initLoader():void
		{
			loader = new Loader();
			
			loader.contentLoaderInfo.addEventListener(Event.INIT, onLoaderInit);
		}
		
		/**
		 * Handles the init event from the loader and adds an event listener for 
		 * the custom module event on the loaded module.
		 */
		private function onLoaderInit(e:Event):void
		{
			var loadedModule:Module = loader.content as Module;
			loadedModule.addEventListener(ModuleEvent.LOAD_COMPLETE, 
			            				  onModuleLoadComplete);
		}

		/**
		 * Handles the load complete event from the module and advances 
		 * the queue.
		 */
		private function onModuleLoadComplete(e:ModuleEvent):void
		{
			var currentOperation:LoadOperation = operations[0];
			currentOperation.setIsLoading(false);
			currentOperation.setIsCompleted(true);
			
			storeLoadedModule(e.getModule());
			
			operations.shift();
			startNextOperationInQueue();
		}
		
		/**
		 * Stores the loaded module in the module registry under the name of the 
		 * current operation.
		 */
		private function storeLoadedModule(loadedModule:Module):void
		{
			var currentOperation:LoadOperation = operations[0];
			
			ModuleRegistry.getInstance().storeModule(
								currentOperation.getModuleName(), loadedModule);
		}

		/**
		 * Loads an swf (which should have a [subclass of] Module as its 
		 * document class) and stores it in the module registry under the 
		 * given name.
		 * Note, this does not automatically start the loading since there might 
		 * be other load operations in the queue before the current one.
		 * ModuleLoader keeps track of all the load operations/instructions it 
		 * receives and processes them in the same order.
		 * A special ModuleLoaderEvent.LOAD_ALL_COMPLETE event is dispatched as 
		 * soon as the loading of the last module is done (i.e if no other
		 * load operations are left in the queue).
		 */
		public function load(moduleName:String, moduleSWFPath:String):void
		{
			queueLoadOperation(new LoadOperation(moduleName, moduleSWFPath));
			
			startNextOperationInQueue();
		}

		/**
		 * Stores a given load operation in the operations queue.
		 */
		private function queueLoadOperation(loadOperation:LoadOperation):void
		{
			if (operations == null)
			{
				operations = new Array();	
			}
			operations.push(loadOperation);
		}
		
		/**
		 * Starts the next operation in the queue. If an operation is already 
		 * running nothing happens. If no more operations exist, 
		 * A ModuleLoaderEvent.LOAD_ALL_COMPLETE event is dispatched.
		 */
		private function startNextOperationInQueue():void
		{
			if (operations.length == 0)
			{
				dispatchEvent(new ModuleLoaderEvent(
										  ModuleLoaderEvent.LOAD_ALL_COMPLETE));
				
				return;
			}
			
			// currentOperation always exists since there a guard clause above 
			// to prevent continuing with an empty queue.
			var currentOperation:LoadOperation = operations[0];
			
		 	if (currentOperation.getIsLoading() == false && 
		 		currentOperation.getIsCompleted() == false)
			{
				startLoadOperation(currentOperation);
			}
		}
		
		/**
		 * Starts loading the module swf from the module swf path stored in the 
		 * given load operation.
		 */
		private function startLoadOperation(loadOperation:LoadOperation):void
		{
			loadOperation.setIsLoading(true);
			
			loader.load(new URLRequest(loadOperation.getModuleSWFPath()), 
					 new LoaderContext(false, ApplicationDomain.currentDomain));
		}
	}
}
	
/**
 * SingletonEnforcer, special class to be able to enforce the use of the above 
 * Singleton class based on the Singleton pattern.
 * This must be placed outside the package to ensure it can only be called from 
 * within this class file.
 */
internal class SingletonEnforcer {}

/**
 * Helper object for storing module names, swf paths  and state variables of a 
 * load operation in a queue.
 */
internal class LoadOperation
{
	private var moduleName:String;
	private var moduleSWFPath:String;
	private var isLoading:Boolean = false;
	private var isCompleted:Boolean = false;

	/**
	 * Creates a new LoadOperation.
	 */
	public function LoadOperation(moduleName:String, moduleSWFPath:String) 
	{
		this.moduleName = moduleName;
		this.moduleSWFPath = moduleSWFPath;
	}
	
	/**
	 * Returns the stored module name for this load operation.
	 */
	public function getModuleName():String
	{
		return moduleName;
	}
	
	/**
	 * Returns the stored module swf path for this load operation.
	 */
	public function getModuleSWFPath():String
	{
		return moduleSWFPath;
	}
	
	/**
	 * Returns whether or not this load operation is currently in progress.
	 */
	public function getIsLoading():Boolean
	{
		return isLoading;
	}
	
	/**
	 * Changes whether or not this load operation is currently in progress 
	 * to the given boolean.
	 */
	public function setIsLoading(isLoading:Boolean):void
	{
		this.isLoading = isLoading;
	}
	
	/**
	 * Returns whether or not this load operation is completed.
	 */
	public function getIsCompleted():Boolean
	{
		return isCompleted;
	}
	
	/**
	 * Changes whether or not this load operation is completed to the 
	 * given boolean.
	 */
	public function setIsCompleted(isCompleted:Boolean):void
	{
		this.isCompleted = isCompleted;
	}
	
	/**
	 * Returns the name of the class in a String including the assigned 
	 * module name.
	 */
	public function toString():String 
	{
		return "LoadOperation[" + moduleName + "]";
	}
}
